/*
    Author: Christopher Diggins
    License: MIT Licence 1.0
    Website: http://www.heron-language.com
*/

#ifndef LLSCHEME_TO_CVML_HPP
#define LLSCHEME_TO_CVML_HPP

namespace lls
{
	struct LLSchemeToCVML {
		ByteCode* bc;
		Subroutine* sub; 

		LLSchemeToCVML(ByteCode* x, Module* mod)
			: bc(x), sub(NULL)
		{ 
			Assert(false, "unimplemented");
			// TODO: deal with GetStatements

			/*

			// Create subroutines for all of the functions
			for (int i=0; i < mod->GetStatements()->GetNumChildren(); ++i) {
				// TODO: eventually support import statements and other statement types
				SExpr* child = mod->GetStatements()->GetChild(i);
				Assert(child->Is<Define>());
				Define* d = child->As<Define>();								
				bc->NewExportedSub(d->GetNameStr());
			}

			mod->GetStatements()->ForEach(*this);
			*/
		}

		void AddQuote(SExpr* x) {
			Subroutine* old = sub;
			sub = bc->NewSub();
			(*this)(x);
			sub->AddInstruction(op_ret);
			old->AddInstruction(op_push_sub, reinterpret_cast<int>(sub), sub->GetName());
			sub = old;
		}

		void ProcessPrimitive(Primitive* x) {
			x->ForEach(*this);
			sub->AddInstruction(PrimitiveIdToOpCode(x->Id()));
		}

		OpCodeEnum PrimitiveIdToOpCode(EnumPrim id) {
			switch (id) 
			{
			case prim_inc: return op_inc;
			case prim_dec: return op_dec;
			case prim_neg: return op_neg;
			case prim_add: return op_add;
			case prim_sub: return op_sub;
			case prim_mul: return op_mul;
			case prim_div: return op_div;
			case prim_mod: return op_mod;
			case prim_gt:  return op_gt;
			case prim_lt:  return op_lt;
			case prim_gteq: return op_gteq;
			case prim_lteq: return op_lteq;
			case prim_eq: return op_eq;
			case prim_neq: return op_neq;
			case prim_and: return op_and;
			case prim_or: return op_or;
			case prim_xor: return op_xor;
			case prim_null: return op_null;
			case prim_nil: return op_nil;
			default:
				Error("Unrecognized enumeration " + ToStr(id));
				return op_noop;
			}
		}

		void AddStringLiteral(ConstString s) {
			int n = bc->AddConstant(s);
			sub->AddInstruction(op_push_string, n, s);
		}

		void operator()(NonNull<SExpr> x) {
			if (x->Is<Define>()) {
				Define* d = x->As<Define>();
				Assert(sub == NULL);
				Subroutine* old = sub;
				String sDef = x->As<Define>()->GetNameStr();
				sub = bc->GetSub(sDef);
				if (sub == NULL) {
					Error("Could not find function " + sDef);
				}
				d->Body()->ForEach(*this);
				// TODO: should I check to make sure that there are no return statements
				sub->AddInstruction(op_ret);
				sub = old;
			}
			else if (x->Is<Apply>()) {
				Apply* a = x->As<Apply>();
				(*this)(a->GetArgs());
				(*this)(a->Func());
				sub->AddInstruction(op_apply, -1, Print(a->Func()));
			}
			else if (x->Is<Args>()) {
				x->ForEach(*this);
			}
			else if (x->Is<Invoke>()) {
				Invoke* a = x->As<Invoke>();
				a->GetArgs()->ForEach(*this);
				(*this)(a->Object());
				AddStringLiteral(a->GetNameStr());
				sub->AddInstruction(op_invoke);
			}
			else if (x->Is<If>()) {
				If* a = x->As<If>();
				(*this)(a->Cond());
				AddQuote(a->TrueBranch());
				AddQuote(a->FalseBranch());
				sub->AddInstruction(op_if);
			}
			else if (x->Is<While>()) {
				While* a = x->As<While>();
				AddQuote(a->Cond());
				AddQuote(a->Body());
				sub->AddInstruction(op_while);
			}
			else if (x->Is<Lambda>()) {
				Error("todo");
			}
			else if (x->Is<Return>()) {
				(*this)(x->As<Return>()->GetVal());
			}
			else if (x->Is<For>()) {
				For* a = x->As<For>();
				(*this)(a->Init());
				AddQuote(a->Inv());
				AddQuote(a->Next());
				AddQuote(a->Body());
				sub->AddInstruction(op_for);
			}
			else if (x->Is<ForEach>()) {
				ForEach* a = x->As<ForEach>();
				(*this)(a->List());
				AddQuote(a->Body());
				sub->AddInstruction(op_foreach);
			}
			else if (x->Is<Pop>()) {
				sub->AddInstruction(op_pop);
			}
			else if (x->Is<GetN>()) {
				sub->AddInstruction(op_getN, x->As<GetN>()->Param(), x->As<GetN>()->name);
			}
			else if (x->Is<SetN>()) {
				sub->AddInstruction(op_setN, x->As<SetN>()->Param(), x->As<SetN>()->name);
			}
			else if (x->Is<ZapN>()) {
				sub->AddInstruction(op_zapN, x->As<ZapN>()->Param(), x->As<ZapN>()->name);
			}
			else if (x->Is<DigN>()) {
				sub->AddInstruction(op_digN, x->As<DigN>()->Param(), x->As<GetN>()->name);
			}
			else if (x->Is<Primitive>()) {
				ProcessPrimitive(x->As<Primitive>());
			}
			else if (x->Is<Literal>()) {
				if (x->Is<PLiteral<int> >()) {
					sub->AddInstruction(op_push_int, x->As<PLiteral<int> >()->Param());
				}
				else if (x->Is<PLiteral<bool> >()) {
					if (x->As<PLiteral<int> >()->Param()) {
						sub->AddInstruction(op_push_true);
					}
					else {
						sub->AddInstruction(op_push_false);
					}
				}
				else if (x->Is<PLiteral<char> >()) {
					char c = x->As<PLiteral<char> >()->Param();
					sub->AddInstruction(op_push_int, static_cast<int>(c));
				}
				else if (x->Is<PLiteral<float> >()) {
					Error("unimplemented");
				}
				else if (x->Is<PLiteral<String> >()) {
					AddStringLiteral(x->As<PLiteral<String> >()->Param());
				}
				else {
					Error("Unsupported literal");
				}
			}
			else if (x->Is<Seq>()) {
				x->ForEach(*this);
			}
			else if (x->Is<GetAt>()) {
				x->ForEach(*this);
				sub->AddInstruction(op_get_at);
			}
			else if (x->Is<SetAt>()) {
				x->ForEach(*this);
				sub->AddInstruction(op_set_at);
			}
			else if (x->Is<GetField>()) {
				GetField* t = x->As<GetField>();
				(*this)(t->Object());
				AddStringLiteral(t->GetNameStr());
				sub->AddInstruction(op_get_field);
			}
			else if (x->Is<SetField>()) {
				SetField* t = x->As<SetField>();
				(*this)(t->Object());
				AddStringLiteral(t->GetNameStr());
				(*this)(t->GetVal());
				sub->AddInstruction(op_set_field);
			}
			else if (x->Is<NoOp>()) {
				// Do nothing
			}
			else if (x->Is<GetF>()) {
				GetF* gf = x->As<GetF>();
				int n = gf->Param();
				sub->AddPushSubInstr(bc->GetSub(n));
				//sub->AddInstruction(op_apply);
			}
			else if (x->Is<External>()) {
				External* ex = x->As<External>();
				AddStringLiteral(ex->GetNameStr());
				sub->AddInstruction(op_external);
			}
			else if (x->Is<New>()) {
				New* nw = x->As<New>();
				(*this)(nw->GetArgs());
				AddStringLiteral(nw->GetType()->data);
				sub->AddInstruction(op_new);
			}
			else {
				Error("unhandled s-expression " + x->ToString());
			}
		}
	};

	void ModuleToCVML(Module* module, ByteCode* bc) {
		LLSchemeToCVML converter(bc, module);
	}
}

#endif
